package v1

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"net/url"
	"time"
	"wxpay/pkg/util"

	"errors"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/google/uuid"
	"github.com/tencentyun/cos-go-sdk-v5"
	"golang.org/x/sync/errgroup"
	"io/ioutil"
	"wxpay/internal/conf"
	"wxpay/internal/service"
	wxpay "wxpay/pkg/api/v1"
	"wxpay/pkg/mcontext"
	"wxpay/pkg/merrors"

	mchApi "wxpay/pkg/wechat_api"
)

// 图片上传 POST /image/upload
func (s *Server) ImageUpload(ctx context.Context, req *wxpay.ImageUploadRequest) (*wxpay.ImageUploadResponse, error) {
	op := merrors.Op("ImageUpload")
	log := mcontext.Logger(ctx)
	log.Info("ImageUpload-start upload")
	defer func() {
		if r := recover(); r != nil {
			log.Info(fmt.Sprintf("ImageUpload-recovery %v", r))
		}
	}()
	apiClient, err := service.NewMchService(s.d).BuildMchApiClient(ctx, req.MchId)
	if err != nil {
		log.Info(fmt.Sprintf("ImageUpload-mchapi error :%s ", err.Error()))
		return nil, err
	}

	// 调用微信接口上传图片
	var g errgroup.Group
	var wxResp *mchApi.MediaUploadResponse
	g.Go(func() error {
		wxResp, err = apiClient.MediaUpload(ctx, mchApi.MediaUploadRequest{Reader: bytes.NewReader(req.Image)})
		if err != nil {
			log.Info(fmt.Sprintf("ImageUpload-weixin upload error :%s ", err.Error()))
			return merrors.E(op, merrors.Invalid, err, merrors.ErrWxApi)
		}
		return nil
	})
	var objName string
	switch conf.Conf.Env.ObjectStorageProvider {
	case "cos":
		objName, err = s.imageStorageByCos(ctx, req, &g)
	case "oss":
		objName, err = s.imageStorageByOss(ctx, req, &g)
	default:
		err = merrors.E(merrors.Op("ImageUpload"), merrors.Invalid, fmt.Errorf("ObjectStorageProvider not match"), merrors.ErrInternal)
	}
	if err != nil {
		return nil, err
	}

	if err := g.Wait(); err != nil {
		log.Info(fmt.Sprintf("ImageUpload-wait-err: %s", err.Error()))
		return nil, err
	}
	log.Info("ImageUpload-wait-ok")

	// 保存objName和mediaID的关联关系
	_, err = s.redis.Set(ctx, wxResp.MediaID, objName, 3*24*time.Hour).Result()
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err, merrors.ErrRedisClient)
	}
	return &wxpay.ImageUploadResponse{MediaId: wxResp.MediaID}, nil
}

func (s *Server) buildBucketCosClient(bucketUrl, secretID, secretKey string) *cos.Client {

	u, _ := url.Parse(bucketUrl)
	b := &cos.BaseURL{
		BucketURL: u,
	}
	client := cos.NewClient(b, &http.Client{
		Transport: &cos.AuthorizationTransport{
			SecretID:  secretID,
			SecretKey: secretKey,
		},
	})
	return client
}

// 腾讯云oss提供图片缓存
func (s *Server) imageStorageByCos(ctx context.Context, req *wxpay.ImageUploadRequest, gPtr *errgroup.Group) (string, error) {
	op := merrors.Op("CosImageUpload")
	log := mcontext.Logger(ctx)
	log.Info("CosImageUpload-start upload")

	cosConf := conf.Conf.Cos

	// u, _ := url.Parse(cosConf.BucketUrl)
	// b := &cos.BaseURL{
	// 	BucketURL: u,
	// }
	// client := cos.NewClient(b, &http.Client{
	// 	Transport: &cos.AuthorizationTransport{
	// 		SecretID:  cosConf.SecretID,
	// 		SecretKey: cosConf.SecretKey,
	// 	},
	// })

	client := s.buildBucketCosClient(cosConf.WxpayBucketUrl, cosConf.SecretID, cosConf.SecretKey)
	log.Info("CosImageUpload-client init ok")

	log.Sugar().Infof("Cos req ------%+v", req)
	objName := genObjectName(req.ImageType)

	log.Info("Cos uploading ------")
	(*gPtr).Go(func() error {
		resp, err := client.Bucket.Head(ctx)
		if err != nil {
			panic(err)
		}
		if resp.StatusCode == 403 {
			log.Info(fmt.Sprintf("CosImageUpload-bucket error :%s ", "无存储桶读取权限"))
			return merrors.E(op, merrors.Permission, err, merrors.ErrCosClient)
		}
		if resp.StatusCode == 404 {
			log.Info(fmt.Sprintf("CosImageUpload-bucket error :%s ", "存储桶不存在"))
			return merrors.E(op, merrors.NotExist, err, merrors.ErrCosClient)
		}
		log.Info("CosImageUpload-bucket ok")
		if _, err = client.Object.Put(ctx, objName, bytes.NewReader(req.Image), nil); err != nil {
			log.Info(fmt.Sprintf("CosImageUpload upload error :%s ", err.Error()))
			return merrors.E(op, merrors.Invalid, err, merrors.ErrCosClient)
		}
		return nil
	})
	return objName, nil
}

// 阿里云oss提供图片缓存
func (s *Server) imageStorageByOss(ctx context.Context, req *wxpay.ImageUploadRequest, gPtr *errgroup.Group) (string, error) {
	op := merrors.Op("OssImageUpload")
	log := mcontext.Logger(ctx)
	log.Info("OssImageUpload-start upload")

	ossConf := conf.Conf.Oss
	// 创建OSSClient实例
	client, err := oss.New(ossConf.Endpoint, ossConf.AccessKeyID, ossConf.AccessKeySecret)
	if err != nil {
		log.Info(fmt.Sprintf("OssImageUpload-oss error :%s ", err.Error()))
		return "", merrors.E(op, merrors.Invalid, err, merrors.ErrOssClient)
	}
	log.Info("OssImageUpload-client init ok")
	// 获取存储空间
	bucket, err := client.Bucket("customer-uploaded-files")
	if err != nil {
		log.Info(fmt.Sprintf("OssImageUpload-bucket error :%s ", err.Error()))
		return "", merrors.E(op, merrors.Invalid, err, merrors.ErrOssClient)
	}
	log.Info("OssImageUpload-bucket init ok")

	objName := genObjectName(req.ImageType)
	(*gPtr).Go(func() error {
		if err = bucket.PutObject(objName, bytes.NewReader(req.Image)); err != nil {
			log.Info(fmt.Sprintf("OssImageUpload upload error :%s ", err.Error()))
			return merrors.E(op, merrors.Invalid, err, merrors.ErrOssClient)
		}
		return nil
	})
	return objName, nil
}

// 上传文件流，以{uuid}.{imageType}作为objectKey
func genObjectName(imageType string) string {
	return fmt.Sprintf("%s.%s", uuid.New().String(), imageType)
}

// 图片上传
func (s *Server) ImageUploadV2(_ context.Context, req *wxpay.ImageUploadV2Request) (*wxpay.ImageUploadV2Response, error) {
	op := merrors.Op("ImageUpload")
	ossConf := conf.Conf.Oss
	// 创建OSSClient实例
	client, err := oss.New(ossConf.Endpoint, ossConf.AccessKeyID, ossConf.AccessKeySecret)
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err, merrors.ErrOssClient)
	}
	// 获取存储空间
	bucket, err := client.Bucket("customer-uploaded-files")
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err, merrors.ErrOssClient)
	}
	objName := fmt.Sprintf("%s.%s", uuid.New().String(), req.ImageType)
	if err = bucket.PutObject(objName, bytes.NewBuffer(req.Image)); err != nil {
		return nil, merrors.E(op, merrors.Invalid, err, merrors.ErrOssClient)
	}
	url := buildOssUrl(objName)
	return &wxpay.ImageUploadV2Response{
		ImageName: objName,
		ImageUrl:  url,
	}, nil
}

// 微信图片上传
func (s *Server) GetWechatMediaId(ctx context.Context, req *wxpay.GetWechatMediaIdReq) (*wxpay.GetWechatMediaIdReply, error) {
	op := merrors.Op("get_wechat_media_id")
	// 下载图片
	// 老的普通http get 不支持 https
	// image, _, err := util.DownloadFile(req.ImageUrl)
	// 新的用cos api 获取对象
	image, _, err := s.imageFetchByCos(ctx, req.ImageUrl)
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err)
	}
	apiClient, err := service.NewMchService(s.d).BuildMchApiClient(ctx, req.MchId)
	if err != nil {
		return nil, err
	}
	wxResp, err := apiClient.MediaUpload(ctx, mchApi.MediaUploadRequest{Reader: bytes.NewReader(image)})
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err, merrors.ErrWxApi)
	}
	_, err = s.redis.Set(ctx, wxResp.MediaID, req.ImageUrl, 3*24*time.Hour).Result()
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err, merrors.ErrRedisClient)
	}
	return &wxpay.GetWechatMediaIdReply{MediaId: wxResp.MediaID}, nil
}

// 腾讯云oss获取图片(已经由前端存储在非wxpay的bucket里)
func (s *Server) imageFetchByCos(ctx context.Context, url string) ([]byte, string, error) {
	fileName, err := util.ParseFileName(url)
	if err != nil {
		return nil, "", err
	}
	log := mcontext.Logger(ctx)
	log.Info("CosImageFetch-start fetch")
	cosConf := conf.Conf.Cos
	client := s.buildBucketCosClient(cosConf.BgpBucketUrl, cosConf.SecretID, cosConf.SecretKey)
	log.Info("CosImageFetch-client init ok")

	log.Info("Cos fetching ------")
	resp, err := client.Object.Get(context.Background(), fileName, nil)
	if err != nil {
		return nil, "", err
	}
	if resp == nil {
		return nil, "", errors.New("no response")
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	body, err := ioutil.ReadAll(resp.Body)

	log.Sugar().Infof("Cos fetched ------%+v", fileName)

	return body, fileName, err
}
